using System;
using UnityEngine;

public class AnalyticFlightTrajectory : Singleton<AnalyticFlightTrajectory>
{
	public struct SequenceSimulationParams
	{
		public PhysicsBall ball;

		public BallFlight flightToSymulateWith;

		public Vector3 landingPos;

		public float spinX;

		public float spinY;

		public float airResistance;

		public float timeToLand;

		public float heightOverNet;

		public bool useNormalTime;

		public float rebound;

		public static SequenceSimulationParams DefaultRecordFlightParams(BallFlight flightToSymulateWith, PhysicsBall ball, Vector3 landingPos, float heightAboveNet, float timeToLand, float spinx, float spiny)
		{
			SequenceSimulationParams result = default(SequenceSimulationParams);
			result.flightToSymulateWith = flightToSymulateWith;
			result.ball = ball;
			result.landingPos = landingPos;
			result.spinX = spinx;
			result.spinY = spiny;
			result.heightOverNet = heightAboveNet;
			result.timeToLand = timeToLand;
			result.useNormalTime = true;
			result.airResistance = 0f;
			result.rebound = 1f;
			return result;
		}
	}

	public void HitBall(FlightSequence sequence, SequenceSimulationParams sim)
	{
		sim.ball.prevPosition = sim.ball.position;
		Vector3 vector = (sim.landingPos - sim.ball.position).OnGround();
		Vector3 vector2 = new Vector3(vector.z, 0f, (0f - vector.x) * sim.spinX);
		vector2.Normalize();
		Vector3 theSpin = new Vector3(Mathf.Sign(vector2.x) * sim.spinX, 0f - sim.spinY, 0f);
		float magnitude = vector.magnitude;
		Vector3 normalized = vector.OnGround().normalized;
		Vector3 vector3 = (0f - sim.airResistance) * 2f * magnitude / (sim.timeToLand * sim.timeToLand) * normalized;
		theSpin += vector3;
		ParabolicFlight parabolicFlight = new ParabolicFlight(sim.ball.position, sim.landingPos, sim.heightOverNet, theSpin, Table.tabletopy);
		float num = parabolicFlight.c / parabolicFlight.g;
		float num2 = Mathf.Sqrt(num);
		if (num > 0f && num2 < 4f && sim.useNormalTime)
		{
			sim.timeToLand = num2;
			GGDebug.DebugLog("NORMAL TIME " + num2 + " SPEED " + Vector3Ex.HorizontalDistance(sim.ball.position, sim.landingPos) / num2);
		}
		else if (sim.timeToLand < num2)
		{
			parabolicFlight.SetSpinYToMatchTime(sim.timeToLand);
			GGDebug.DebugLog("Spin to match time " + parabolicFlight.spin);
		}
		else if (sim.timeToLand > num2)
		{
			float b = parabolicFlight.hnToMatchTime(sim.timeToLand);
			parabolicFlight.hn = Mathf.Min(parabolicFlight.hn + 0.2f, b);
			parabolicFlight.SetSpinYToMatchTime(sim.timeToLand);
			GGDebug.DebugLog("height to match time " + parabolicFlight.hn);
		}
		GGDebug.DebugLog("SPEED " + Vector3Ex.HorizontalDistance(sim.ball.position, sim.landingPos) / sim.timeToLand);
		float timeToLand = sim.timeToLand;
		Vector3 velocity = parabolicFlight.initialVelocityWithTime(timeToLand);
		Vector3 spin = parabolicFlight.spin;
		GGDebug.DebugLog("SPIN " + spin);
		sequence.ClearStages();
		FlightPool flightPool = SingletonInit<FlightPool>.instance;
		GravityBallFlight gravityBallFlight = flightPool.GetFlightFromPool(sim.flightToSymulateWith.GetType()) as GravityBallFlight;
		if (gravityBallFlight == null)
		{
			throw new InvalidCastException("ball flight is wrong type");
		}
		gravityBallFlight.gravity = Physics.gravity + spin;
		FlightParams flightParams = default(FlightParams);
		flightParams.position = sim.ball.position;
		flightParams.velocity = velocity;
		flightParams.angularVelocity = new Vector3(sim.spinX, sim.spinY, 0f);
		FlightParams flightParams2 = flightParams;
		gravityBallFlight.SetInitialConditions(flightParams2, null);
		gravityBallFlight.Reset();
		float duration = ((!Table.IsPosOnTable(sim.landingPos)) ? DurationTillOutOfRange(gravityBallFlight) : sim.timeToLand);
		sequence.AddStage(gravityBallFlight, flightParams2, duration, false, (!Table.IsPosOnTable(sim.landingPos)) ? HitObjectTag.Floor : HitObjectTag.Table);
		sequence.timeToLand = sim.timeToLand;
		sequence.landingPos = sim.landingPos;
		if (Table.IsPosOnTable(sim.landingPos))
		{
			Vector3 velocity2 = Rebound(gravityBallFlight.VelocityAt(sim.timeToLand), sim.ball.tablePhysicMaterial, sim.ball.myMaterial);
			Vector3 position = gravityBallFlight.PositionAt(sim.timeToLand);
			velocity2.x *= sim.rebound;
			velocity2.z *= sim.rebound;
			velocity2.y = Mathf.Sqrt(-2f * Physics.gravity.y * 0.2f);
			GravityBallFlight gravityBallFlight2 = flightPool.GetFlightFromPool(sim.flightToSymulateWith.GetType()) as GravityBallFlight;
			if (gravityBallFlight2 == null)
			{
				throw new InvalidCastException("ball flight is wrong type");
			}
			gravityBallFlight2.gravity = Physics.gravity;
			flightParams = default(FlightParams);
			flightParams.position = position;
			flightParams.velocity = velocity2;
			flightParams.angularVelocity = new Vector3(sim.spinX, sim.spinY, 0f);
			FlightParams flightParams3 = flightParams;
			gravityBallFlight2.SetInitialConditions(flightParams3, null);
			gravityBallFlight2.Reset();
			float num3 = ((!Table.IsPosOnTable(gravityBallFlight2.PositionAt(2f * gravityBallFlight2.apexTime))) ? DurationTillOutOfRange(gravityBallFlight2) : (2f * gravityBallFlight2.apexTime));
			sequence.AddStage(gravityBallFlight2, flightParams3, num3, false, (!Table.IsPosOnTable(gravityBallFlight2.PositionAt(num3))) ? HitObjectTag.Floor : HitObjectTag.Table);
		}
	}

	private float DurationTillOutOfRange(GravityBallFlight flight)
	{
		float num = Table.halfwidth * 2f;
		float num2 = Table.halfLength * 1.5f;
		float c = Table.tabletopy - flight.radius;
		Vector3 initialPosition = flight.initialPosition;
		Vector3 initialVelocity = flight.initialVelocity;
		float a = 200f;
		if (initialVelocity.x != 0f)
		{
			MathEx.NullPoints onlyPositive = MathEx.FindParabolaNullPoints(flight.gravity.x * 0.5f, initialVelocity.x, Mathf.Sign(initialVelocity.x) * num - initialPosition.x).onlyPositive;
			if (onlyPositive.nullPts.Count > 0)
			{
				a = Mathf.Min(a, onlyPositive.Min);
			}
		}
		if (initialVelocity.z != 0f)
		{
			MathEx.NullPoints onlyPositive2 = MathEx.FindParabolaNullPoints(flight.gravity.z * 0.5f, initialVelocity.z, Mathf.Sign(initialVelocity.z) * num2 - initialPosition.z).onlyPositive;
			if (onlyPositive2.nullPts.Count > 0)
			{
				a = Mathf.Min(a, onlyPositive2.Min);
			}
		}
		MathEx.NullPoints onlyPositive3 = MathEx.FindParabolaNullPoints(flight.gravity.y * 0.5f, initialVelocity.y, c).onlyPositive;
		return Mathf.Min(a, onlyPositive3.Min);
	}

	private Vector3 Rebound(Vector3 velocity, PhysicMaterial hitMaterial, PhysicMaterial ballMaterial)
	{
		velocity.y *= -1f * CombineBounce(hitMaterial, ballMaterial);
		return velocity;
	}

	private float CombineBounce(PhysicMaterial hitMaterial, PhysicMaterial ballMaterial)
	{
		if (hitMaterial == null)
		{
			return ballMaterial.bounciness;
		}
		switch (hitMaterial.bounceCombine)
		{
		case PhysicMaterialCombine.Average:
			return (ballMaterial.bounciness + hitMaterial.bounciness) * 0.5f;
		case PhysicMaterialCombine.Maximum:
			return Mathf.Max(ballMaterial.bounciness, hitMaterial.bounciness);
		case PhysicMaterialCombine.Minimum:
			return Mathf.Min(ballMaterial.bounciness, hitMaterial.bounciness);
		case PhysicMaterialCombine.Multiply:
			return ballMaterial.bounciness * hitMaterial.bounciness;
		default:
			return ballMaterial.bounciness;
		}
	}

	public void HitServe(FlightSequence sequence, SequenceSimulationParams sim)
	{
		Vector3 position = sim.ball.position;
		float num = position.y - Table.tabletopy;
		float timeToLand = sim.timeToLand;
		float num2 = 1f / sim.ball.myMaterial.bounciness;
		float num3 = Mathf.Max(sim.heightOverNet, 1.1f * num / (num2 * num2));
		float num4 = timeToLand * timeToLand;
		float num5 = num4 * num4;
		float num6 = (2f + num2) * (2f + num2);
		float num7 = 4f * num4 * num6;
		float num8 = num2 * num2;
		float num9 = -2f * num3 * (num6 - num8) - 2f * num;
		float a = num5;
		float b = 2f * (num4 * num9 + num7 * num3);
		float c = num9 * num9;
		float num10 = Physics.gravity.y;
		Vector3 zero = Vector3.zero;
		Vector3 vector = (sim.landingPos - sim.ball.position).OnGround();
		Vector3 vector2 = new Vector3(vector.z, 0f, (0f - vector.x) * sim.spinX);
		vector2.Normalize();
		Vector3 vector3 = new Vector3(Mathf.Sign(vector2.x) * sim.spinX, 0f, 0f);
		float magnitude = vector.magnitude;
		Vector3 normalized = vector.OnGround().normalized;
		Vector3 vector4 = (0f - sim.airResistance) * 2f * magnitude / (sim.timeToLand * sim.timeToLand) * normalized;
		vector3 += vector4;
		zero = vector3;
		float num11 = Mathf.Sqrt(-2f * num10 * num3);
		float num12 = -2f * num11 / num10;
		float min = MathEx.FindParabolaNullPoints((0f - num10) / 2f, (0f - num2) * num11, num).onlyPositive.Min;
		if (num12 + min > sim.timeToLand)
		{
			MathEx.NullPoints nullPoints = MathEx.FindParabolaNullPoints(a, b, c);
			foreach (float nullPt in nullPoints.nullPts)
			{
				if (!(nullPt > 0f))
				{
					zero.y = nullPt - Physics.gravity.y;
					num10 = nullPt;
					if (nullPt < Physics.gravity.y)
					{
						break;
					}
				}
			}
		}
		else
		{
			float num13 = -2f * (num6 - num8);
			float num14 = num4 * num10 - 2f * num;
			MathEx.NullPoints onlyPositive = MathEx.FindParabolaNullPoints(num13 * num13, 2f * (num14 * num13 + num7 * num10), num14 * num14).onlyPositive;
			foreach (float nullPt2 in onlyPositive.nullPts)
			{
				num3 = Mathf.Max(num3, nullPt2);
			}
			num3 = Mathf.Max(num3, 1.1f * num / (num2 * num2));
		}
		num11 = Mathf.Sqrt(-2f * num10 * num3);
		num12 = -2f * num11 / num10;
		min = MathEx.FindParabolaNullPoints((0f - num10) / 2f, (0f - num2) * num11, num).onlyPositive.Min;
		sim.timeToLand = min + num12;
		float y = (0f - num10) * min - num2 * num11;
		float num15 = sim.landingPos.x - position.x;
		float num16 = sim.landingPos.z - position.z;
		float x = num15 / sim.timeToLand - zero.x * sim.timeToLand / 2f;
		float z = num16 / sim.timeToLand - zero.z * sim.timeToLand / 2f;
		Vector3 vector5 = new Vector3(x, y, z);
		Vector3 velocity = vector5;
		velocity.x += zero.x * min;
		velocity.z += zero.z * min;
		velocity.y = num11;
		sequence.ClearStages();
		FlightPool flightPool = SingletonInit<FlightPool>.instance;
		GravityBallFlight gravityBallFlight = flightPool.GetFlightFromPool(sim.flightToSymulateWith.GetType()) as GravityBallFlight;
		if (gravityBallFlight == null)
		{
			throw new InvalidCastException("ball flight is wrong type");
		}
		gravityBallFlight.gravity = Physics.gravity + zero;
		FlightParams flightParams = default(FlightParams);
		flightParams.position = sim.ball.position;
		flightParams.velocity = vector5;
		flightParams.angularVelocity = new Vector3(sim.spinX, sim.spinY, 0f);
		FlightParams flightParams2 = flightParams;
		gravityBallFlight.SetInitialConditions(flightParams2, null);
		gravityBallFlight.Reset();
		sequence.AddStage(gravityBallFlight, flightParams2, min, false, HitObjectTag.Table);
		Vector3 position2 = gravityBallFlight.PositionAt(min);
		sequence.timeToLand = sim.timeToLand;
		GravityBallFlight gravityBallFlight2 = flightPool.GetFlightFromPool(sim.flightToSymulateWith.GetType()) as GravityBallFlight;
		if (gravityBallFlight2 == null)
		{
			throw new InvalidCastException("ball flight is wrong type");
		}
		gravityBallFlight2.gravity = Physics.gravity + zero;
		flightParams = default(FlightParams);
		flightParams.position = position2;
		flightParams.velocity = velocity;
		flightParams.angularVelocity = new Vector3(sim.spinX, sim.spinY, 0f);
		FlightParams flightParams3 = flightParams;
		gravityBallFlight2.SetInitialConditions(flightParams3, null);
		gravityBallFlight2.Reset();
		float duration = ((!Table.IsPosOnTable(sim.landingPos)) ? DurationTillOutOfRange(gravityBallFlight2) : num12);
		sequence.AddStage(gravityBallFlight2, flightParams3, duration, false, (!Table.IsPosOnTable(sim.landingPos)) ? HitObjectTag.Floor : HitObjectTag.Table);
		sequence.landingPos = sim.landingPos;
		if (Table.IsPosOnTable(sim.landingPos))
		{
			Vector3 velocity2 = Rebound(gravityBallFlight2.VelocityAt(num12), sim.ball.tablePhysicMaterial, sim.ball.myMaterial);
			Vector3 position3 = gravityBallFlight2.PositionAt(num12);
			velocity2.x *= sim.rebound;
			velocity2.z *= sim.rebound;
			velocity2.y = Mathf.Sqrt(-2f * Physics.gravity.y * 0.2f);
			GravityBallFlight gravityBallFlight3 = flightPool.GetFlightFromPool(sim.flightToSymulateWith.GetType()) as GravityBallFlight;
			if (gravityBallFlight3 == null)
			{
				throw new InvalidCastException("ball flight is wrong type");
			}
			gravityBallFlight3.gravity = Physics.gravity;
			flightParams = default(FlightParams);
			flightParams.position = position3;
			flightParams.velocity = velocity2;
			flightParams.angularVelocity = new Vector3(sim.spinX, sim.spinY, 0f);
			FlightParams flightParams4 = flightParams;
			gravityBallFlight3.SetInitialConditions(flightParams4, null);
			gravityBallFlight3.Reset();
			float num17 = ((!Table.IsPosOnTable(gravityBallFlight3.PositionAt(2f * gravityBallFlight3.apexTime))) ? DurationTillOutOfRange(gravityBallFlight3) : (2f * gravityBallFlight3.apexTime));
			sequence.AddStage(gravityBallFlight3, flightParams4, num17, false, (!Table.IsPosOnTable(gravityBallFlight3.PositionAt(num17))) ? HitObjectTag.Floor : HitObjectTag.Table);
		}
	}
}
